package com.ihr360.shared.encryption.convertor;

import com.ihr360.commons.lang.StringUtils;
import com.ihr360.encryption.KeyProperty;
import com.ihr360.encryption.MysqlCipherInitializer;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.persistence.AttributeConverter;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;


abstract class AbstractCryptoConverter<T> implements AttributeConverter<T, String> {

    private MysqlCipherInitializer cipherInitializer;

    public AbstractCryptoConverter() {
        this(new MysqlCipherInitializer());
    }

    public AbstractCryptoConverter(MysqlCipherInitializer cipherInitializer) {
        this.cipherInitializer = cipherInitializer;
    }

    @Override
    public String convertToDatabaseColumn(T attribute) {
        if (StringUtils.isNotEmpty(KeyProperty.DATABASE_ENCRYPTION_KEY) && isNotNullOrEmpty(attribute)) {
            try {
                Cipher cipher = cipherInitializer.prepareAndInitCipher(Cipher.ENCRYPT_MODE, KeyProperty.DATABASE_ENCRYPTION_KEY);
                return encrypt(cipher, attribute);
            } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        }
        return entityAttributeToString(attribute);
    }

    @Override
    public T convertToEntityAttribute(String dbData) {
        if (StringUtils.isNotEmpty(KeyProperty.DATABASE_ENCRYPTION_KEY) && StringUtils.isNotEmpty(dbData)) {
            try {
                Cipher cipher = cipherInitializer.prepareAndInitCipher(Cipher.DECRYPT_MODE, KeyProperty.DATABASE_ENCRYPTION_KEY);
                return decrypt(cipher, dbData);
            } catch (NoSuchAlgorithmException
                    | InvalidKeyException
                    | InvalidAlgorithmParameterException
                    | BadPaddingException
                    | NoSuchPaddingException
                    | IllegalBlockSizeException
                    | DecoderException e) {
                throw new RuntimeException(e);
            }
        }
        return stringToEntityAttribute(dbData);
    }

    abstract boolean isNotNullOrEmpty(T attribute);

    abstract T stringToEntityAttribute(String dbData);

    abstract String entityAttributeToString(T attribute);

    byte[] callCipherDoFinal(Cipher cipher, byte[] bytes) throws IllegalBlockSizeException, BadPaddingException {
        return cipher.doFinal(bytes);
    }

    private String encrypt(Cipher cipher, T attribute) throws IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
        byte[] bytesToEncrypt = entityAttributeToString(attribute).getBytes("UTF-8");
        byte[] encryptedBytes = callCipherDoFinal(cipher, bytesToEncrypt);
        return new String(Hex.encodeHex(encryptedBytes));
    }

    private T decrypt(Cipher cipher, String dbData) throws IllegalBlockSizeException, BadPaddingException, DecoderException {
        byte[] encryptedBytes = Hex.decodeHex(dbData.toCharArray());
        byte[] decryptedBytes = callCipherDoFinal(cipher, encryptedBytes);
        return stringToEntityAttribute(new String(decryptedBytes));
    }
}
